package com.zyg.search.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.zyg.search.entity.ItemEntity;
import com.zyg.search.entity.ItemVo;
import com.zyg.search.service.ItemSearchService;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * ------------------------------
 * 作者: LMY
 * 类-功能: XX
 * 创建时间: 2021-11-02 22:55
 * ------------------------------
 **/
@Service
public class ItemSearchServiceImpl implements ItemSearchService {

    @Autowired
    private ElasticsearchRestTemplate restTemplate;
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 作者 : LMY
     * 功能 : 条件查询
     * 时间 : 2021/11/2 23:56
     */
    @Override
    public Map<String, Object> search(ItemVo itemVo) {
        System.out.println("itemVo = " + itemVo);
        System.out.println(" ----------------------------------------------------------");

        //第一部分：-------------------------- 查询部分 --------------------------------
        //1.1 定义一个存放结果的Map集合
        Map<String, Object> resultMap = new HashMap<>();
        //1.2 拿到查询的关键字
        String keywords = itemVo.getKeywords();
        if (StrUtil.isBlank(keywords)) {  //如果为空，给个默认值 华为
            keywords = "";
        }

        //1.3 拿到本地搜索建造器
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();

        //1.4 将关键字进行多字段查询
        builder.withQuery(QueryBuilders.multiMatchQuery(keywords, "title", "category", "brand"));

        //1.5 分组查询，按分类字段分组
        builder.addAggregation(AggregationBuilders.terms("categoryGroup").field("category.keyword").size(50));

        //1.6 添加高亮查询
        builder.withHighlightBuilder(new HighlightBuilder().field("title")
                .preTags("<span style='color:red'>")   //前缀
                .postTags("</span>"));                 //后缀


        //1.7 进行过滤查询
        //1.7.1 进行分类查询
        //定义用于组合多个查询条件的BooleanQuery对象
        BoolQueryBuilder booleanQuery = QueryBuilders.boolQuery();

        if (StrUtil.isNotBlank(itemVo.getCategory())) {
            booleanQuery.filter(QueryBuilders.termQuery("category.keyword", itemVo.getCategory()));
        }

        //1.7.2 进行品牌查询
        if (StrUtil.isNotBlank(itemVo.getBrand())) {

            booleanQuery.filter(QueryBuilders.termQuery("brand.keyword", itemVo.getBrand()));
        }

        //1.7.3 进行规格查询
        if (itemVo.getSpec() != null && itemVo.getSpec().size() > 0) {
            //① 得到规格的内容
            List<String> spec = itemVo.getSpec();
            //② 遍历集合，添加过滤查询条件
            for (String s : spec) {
                //1. 进行拆分，得到key（字段名--》specMap.key）与value：
                String[] split = s.split(":");
                //3. 添加过滤查询条件
                booleanQuery.filter(QueryBuilders.termQuery("specMap." + split[0] + ".keyword", split[1]));
            }
        }


        //1.7.4 价格区间查询
        //① 拿到价格区间
        String price = itemVo.getPrice();
        if (StrUtil.isNotBlank(price)) {
            //② 此时价格区间是 0-500 我们按-拆分出 0/500两个区间的值
            String[] split = price.split("-");
            //③ 判断是不是最大值
            if (split[1].equals("*")) {  //如果是最大值，只过滤出大于最小区间的
                booleanQuery.filter(QueryBuilders.rangeQuery("price").gte(split[0]));
            } else {                      //不是最大值，过滤出大于最小区间的且小于等于最大区间值的数据
                booleanQuery.filter(QueryBuilders.rangeQuery("price").gt(split[0]).lte(split[1]));
            }
        }

        //1.8 按字段进行排序
        String sortAndField = itemVo.getSort();
        if (StrUtil.isNotBlank(sortAndField)) {
            //1.8.1 进行字符串拆分
            String[] sortSplit = sortAndField.split(":");
            //1.8.2 按字段与排序规则进行排序
            builder.withSort(SortBuilders.fieldSort(sortSplit[0]).order("asc".equals(sortSplit[1]) ? SortOrder.ASC :SortOrder.DESC));
        }

        //1.9 进行分页查询
        int page = itemVo.getPage();
        int pageSize = itemVo.getPageSize();


        //第二部分：查询器融合各种查询处理器，如过滤器，
        //2.1查询器融合过滤器
        builder.withFilter(booleanQuery);
        builder.withPageable(PageRequest.of(page-1,pageSize));

        //2.4 拿到查询器获取查询对象
        Query query = builder.build();


        //第三部分：-------------------------- 对查询结果进行分析 --------------
        //3.0 得到查询命中的对象
        SearchHits<ItemEntity> searchHits = restTemplate.search(query, ItemEntity.class, IndexCoordinates.of("item"));

        //记录的总数
        long total = searchHits.getTotalHits();
        //总页数
        int totalPage = (int) Math.ceil(total/(double)itemVo.getPageSize());


        //3.1 定义存放高量查询结果的集合
        List<ItemEntity> highlights = new ArrayList<>();
        //3.3. 得到搜索命中的对象集合
        List<SearchHit<ItemEntity>> hits = searchHits.getSearchHits();
        //3.3 遍历
        for (SearchHit<ItemEntity> hit : hits) {
            //① 得到高量数据
            List<String> title = hit.getHighlightField("title");
            //② 得到高亮前的数据
            ItemEntity content = hit.getContent();
            //③ 非空判断，并重设
            if (title != null && title.size() > 0) {
                content.setTitle(title.get(0));
            }
            highlights.add(content);
        }

        System.out.println("高量数据：" + highlights);

        //3.2 拿到聚合对象
        Aggregations aggregations = searchHits.getAggregations();
        //3.3 取出分组名为 categoryGroup的数据
        ParsedStringTerms categoryGroup = aggregations.get("categoryGroup");
        //3.4 拿到桶
        List<? extends Terms.Bucket> buckets = categoryGroup.getBuckets();
        System.out.println("buckets = " + buckets);

        //3.5 遍历这个桶,查询出分类列表
        List<String> categoryList = buckets.stream().map(m ->  m.getKeyAsString()).collect(Collectors.toList());


        //3.6. 拿到分类参数,进行非空判断，如果为空给个默认值
        String category = itemVo.getCategory();
        Map brandAnSpecList = new HashMap();
        if (StrUtil.isBlank(category)) {
            if (CollectionUtil.isNotEmpty(categoryList)) {
                category = categoryList.get(0);  //默认值是第一个分类
                brandAnSpecList = findBrandAnSpec(category);
            } else {
                category = "";
                brandAnSpecList.put("brandList", new ArrayList<>());
                brandAnSpecList.put("specList", new ArrayList<>());
            }

        }
        //13. 根据分类关键字在Redis中查询出品牌列表

        // 添加查询结果
        resultMap.putAll(brandAnSpecList);
        // 添加分类列表放入集合中
        resultMap.put("categoryList", categoryList);
        // 添加高量数据合
        resultMap.put("rows", highlights);
        //添加分页结果
        resultMap.put("total",total);
        resultMap.put("totalPage",totalPage);
        

        //16. 返回大集合
        return resultMap;
    }

    /**
     * 作者 : LMY
     * 功能 : 根据分类名称，在Redis中查询出品牌，以及规格列表
     * 时间 : 2021/11/3 0:36
     */
    private Map findBrandAnSpec(String category) {
        //1. 根据分类名查询出模板的id
        String typeId = redisTemplate.boundHashOps("itemCatList").get(category + "") + "";
        //3. 根据模板id查询出品牌列表
        String brandIds = redisTemplate.boundHashOps("brandList").get(typeId + "") + "";
        //3. 查询出规格列表
        String specIds = redisTemplate.boundHashOps("specList").get(typeId + "") + "";

        //4. 转换为集合
        List<Map> brandList = JSON.parseArray(brandIds, Map.class);
        List<Map> specList = JSON.parseArray(specIds, Map.class);

        //5. 存入Map中返回
        Map map = new HashMap();
        map.put("brandList", brandList);
        map.put("specList", specList);

        return map;
    }
}
